package IC.Tables.Types;

import java.util.*;
import IC.AST.*;
import IC.Semantic.SemanticError;

/**
 * Main class which holds the TypeTable for the input program
 */

public class TypeTable {
	private static Map<String,Type> primitiveTypes = new HashMap<String,Type>();
    private static Map<String,ClassType> classTypes = new HashMap<String,ClassType>();
    private static Map<Type,ArrayType> arrayTypes = new HashMap<Type,ArrayType>();
    private static Map<String,MethodType> methodTypes = new HashMap<String,MethodType>();
  
    
	
    private static String icFileName = null;
    
    /**
     * initialize 
     */
    public static void initTypeTable(String icFileName){
    	primitiveTypes.put("int", new IntType());
    	primitiveTypes.put("boolean", new BoolType());
    	primitiveTypes.put("null", new NullType());
    	primitiveTypes.put("string", new StringType());
    	primitiveTypes.put("void", new VoidType());
    	TypeTable.icFileName = icFileName;
    }
    
    /**
     * getter for the IC program file name
     */
    public static String getFileName(){
    	return icFileName;
    }

    /**
     *  Returns unique array type object
     */
    public static ArrayType getArrayType(Type elemType) {
    	if (!arrayTypes.containsKey(elemType))
    	{
    		 arrayTypes.put(elemType,new ArrayType(elemType));
    	}
    	return arrayTypes.get(elemType);    	

    }
    
    /**
     * Adds a new ClassType entry to TypeTable. 
     * Throws a SemanticError if the class is already defined in the table
     * or if it extends a class that was not previously defined. 
     */
    public static void addClassType(ICClass c) throws SemanticError{
    	if (classTypes.containsKey(c.getName())){ 
    		throw new SemanticError("the class was already defined",c.getLine(),c.getName());
    	}
    	if (c.hasSuperClass()) {
    		if (!classTypes.containsKey(c.getSuperClassName()))
    			throw new SemanticError("the super-class of the class is undefined",c.getLine(),c.getSuperClassName());
    	}
    	
      	classTypes.put(c.getName(),new ClassType(c));
    }
    
    /** 
     * Returns unique class type object
     */
    public static ClassType getClassType(String name) throws SemanticError{
    	ClassType ct = classTypes.get(name);
    	if (ct == null) 
    		throw new SemanticError("the class is undefined",name);
    	else 
    		return ct;
    }
    
    public static MethodType getMethodType(Type returnType, List<Type> paramTypes){
    	MethodType mt = new MethodType(returnType,paramTypes);
    	String key = mt.toString();
    	
    	if (!methodTypes.containsKey(key))
    	{
    		methodTypes.put(key, mt);
    	}
    	return methodTypes.get(key);   	

    	
    }
    
    /**
     * A getter that gets a String and returns the type
     */
    public static Type getType(String typeName) throws SemanticError{
    	
    	//Primitive
    	if (primitiveTypes.containsKey(typeName))
    		return primitiveTypes.get(typeName);
    	// array
    	if (typeName.endsWith("[]")) return 
    		getArrayType(getType(typeName.substring(0, typeName.length()-2)));
    	// case class type
    	else return getClassType(typeName);
    }
    
    /**
     * returns string representation for the TypeTable in case the "-dump-symtab" IC.Compiler flag was raised
     */
    public static String dumpSymbolTable(){
    	String str = "Type Table: "+icFileName+"\n";
    	
    	// construct string representation for primitive types
    	Iterator<Type> primitiveTypesIter = primitiveTypes.values().iterator();
    	String primitiveTypesStr = "";
    	while (primitiveTypesIter.hasNext()){
    		Type t = primitiveTypesIter.next();
    		primitiveTypesStr += "\t"+t.getTypeID()+": Primitive type: "+t.getName()+"\n";
    	}
    	
    	// construct string representation for class types
    	Iterator<ClassType> classTypesIter = classTypes.values().iterator();
    	String classTypesStr = "";
    	while (classTypesIter.hasNext()){
    		ClassType ct = classTypesIter.next();
    		classTypesStr += "\t"+ct.getTypeID()+": Class: "+ct.toString()+"\n";
    	}
    	
    	// construct string representation for array types
    	Iterator<ArrayType> arrayTypesIter = arrayTypes.values().iterator();
    	String arrayTypesStr = "";
    	while (arrayTypesIter.hasNext()){
    		ArrayType at = arrayTypesIter.next();
    		arrayTypesStr += "\t"+at.getTypeID()+": Array type: "+at.toString()+"\n";
    	}
    	
    	// construct string representation for method types
    	String methodTypesStr = "";
    	for (MethodType mt: methodTypes.values()){
    		methodTypesStr += "\t"+mt.getTypeID()+": Method type: "+mt.toString()+"\n";
    	}
    	
    	str += primitiveTypesStr+classTypesStr+arrayTypesStr+methodTypesStr;
    	return str;
    }
    
    /**
     * Checks if the name is of a primitive type (except for null or string).
     */
    public static boolean isPrimitive(String name){
    	return ((name == "int") || (name == "boolean") || (name == "void"));
    }
    
}
